package org.zzz.hepotaiya.loader;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.log4j.Logger;
import org.zzz.hepotaiya.annotation.Depencies;
import org.zzz.hepotaiya.annotation.Entry;
import org.zzz.hepotaiya.beans.BatchInfo;
import org.zzz.hepotaiya.config.BatchConfig;
import org.zzz.hepotaiya.container.IContainer;
import org.zzz.hepotaiya.exception.BatchLoadException;
import org.zzz.hepotaiya.util.ClassUtils;
import org.zzz.hepotaiya.util.ResourceUtil;

/**
 * 
 *
 */
public class BatchLoaderImpl implements BatchLoader {

	private static final Logger logger = Logger.getLogger(BatchLoaderImpl.class);

	Map<Integer, List<BatchInfo>> phaseMap = new HashMap<Integer, List<BatchInfo>>();

	IContainer container;
	
	public void setContainer(IContainer container) {
		this.container = container;
	}

	/*
	 * (non-Javadoc)
	 * @see org.zzz.hepotaiya.loader.BatchLoader#load(java.lang.String, java.util.List)
	 */
	public Map<Integer, List<BatchInfo>> load(String batchRootPackage, List<BatchConfig> list) throws BatchLoadException {
		if (null == list) {
			return this.load(batchRootPackage);
		}
		try {
			List<Class> classList = ClassUtils.findLoadedClasses(batchRootPackage);
			for (BatchConfig config : list) {
				String batchClassName = StringUtils.substringBefore(config.batchName, "#");
				String batchMethodName = StringUtils.substringAfter(config.batchName, "#");
				if (StringUtils.isEmpty(batchClassName) || StringUtils.isEmpty(batchMethodName)) {
					throw new BatchLoadException(ResourceUtil.getString("loader.exception.emptyname", batchClassName, batchMethodName));
				}
				for (int i = 0; i < classList.size(); i++) {
					Class clazz = classList.get(i);
					String className = StringUtils.uncapitalize(clazz.getSimpleName());
					if (!batchClassName.equals(className)) {
						continue;
					}
					Method method = clazz.getMethod(batchMethodName);
					this.entryBatch(clazz, method, config.phase, config.index, config.isErrorStop, config.depencyMap);
				}
			}
		} catch (Exception e) {
			throw new BatchLoadException(e);
		}
		return this.phaseMap;
	}
	
	/**
	 * アノテーション設定読み込み
	 * @param batchRootPackage
	 * @return
	 * @throws BatchLoadException
	 */
	Map<Integer, List<BatchInfo>> load(String batchRootPackage) throws BatchLoadException {
		try {
			List<Class> classList = ClassUtils.findLoadedClasses(batchRootPackage);
			for (int i = 0; i < classList.size(); i++) {
				Class clazz = classList.get(i);
				// バッチ判断
				for (Method method : clazz.getMethods()) {
					Entry annEntry = (Entry)method.getAnnotation(Entry.class);
					if (null == annEntry) {
						continue;
					}
					Depencies annDepency = (Depencies)method.getAnnotation(Depencies.class);
					Map<String, Boolean> depencyMap = this.createDepencyMap(annDepency);
					this.entryBatch(clazz, method, annEntry.phase(), annEntry.index(), annEntry.isErrorStop(), depencyMap);
				}
			}
		} catch (Exception e) {
			throw new BatchLoadException(e);
		}
		return this.phaseMap;
	}
	
	/**
	 * Annotationから依存関係Mapを生成します
	 * @param depencyAnnotation
	 * @return
	 */
	Map<String, Boolean> createDepencyMap(Depencies depencyAnnotation) {
		Map<String, Boolean> ret = new HashMap<String, Boolean>();
		if (depencyAnnotation == null) {
			return ret;
		}
		for (String batchName : depencyAnnotation.success()) {
			ret.put(batchName, true);
		}
		for (String batchName : depencyAnnotation.fail()) {
			ret.put(batchName, false);
		}
		return ret;
	}

	/**
	 * バッチを登録します
	 * 
	 * @param 実行対象Class
	 * @param 実行対象Method
	 * @param フェーズ
	 * @param 実行順序
	 * @param 異常終了判定
	 * @param 依存関係Map
	 * @throws BatchLoadException 
	 */
	void entryBatch(Class clazz, Method method,
			int phase, int index, boolean isErrorStop, Map<String, Boolean> dependencyMap) throws BatchLoadException {
		int phaseIndex = phase;
		if (!this.phaseMap.containsKey(phaseIndex)) {
			this.phaseMap.put(phaseIndex, new ArrayList<BatchInfo>());
		}
		BatchInfo info = new BatchInfo();
		info.setTargetMethod(method);
		info.setTargetClass(clazz);
		//実行対象取得
		try {
			info.setTargetObject(this.container.getObject(info.getComponentName()));
			if (null == info.getTargetObject()) {
				info.setTargetObject(this.container.getObject(info.getTargetClass()));
			}
		} catch (Exception e) {
			throw new BatchLoadException(e);
		}
		if (null == info.getTargetObject()) {
			throw new BatchLoadException(ResourceUtil.getString("loader.exception.nulltarget"));
		}
			
		info.setComponentName(StringUtils.uncapitalize(clazz.getSimpleName()));
		info.setBatchName(StringUtils.uncapitalize(clazz.getSimpleName()) + "#" + method.getName());
		info.setErrorStop(isErrorStop);
		info.setDependencies(dependencyMap);
		info.setIndex(index);
		info.setPhase(phase);
		
		if (logger.isDebugEnabled()) {
			logger.debug(ResourceUtil.getString("loader.log.entry", info.getBatchName(), phaseIndex, ToStringBuilder.reflectionToString(info)));
		}
		if (index == -1) {
			this.phaseMap.get(phaseIndex).add(info);
			return;
		}
		this.setIndex(this.phaseMap.get(phaseIndex), info);
	}
	
	/**
	 * BatchInfo.indexによりList内の順序を設定し、リストに追加します。
	 * @param list
	 * @param batchInfo
	 */
	void setIndex(List<BatchInfo> list, BatchInfo batchInfo) {
		for (int i = 0; i < list.size(); i ++) {
			if (list.get(i).getIndex() >= batchInfo.getIndex()) {
				list.add(i, batchInfo);
				return;
			}
			if (list.size() -1 - i >= 0) {
				if (list.get(list.size() -1 - i).getIndex() <= batchInfo.getIndex()) {
					list.add(list.size() - i, batchInfo);
					return;
				}
			}
		}
		list.add(batchInfo);
	}
}
